#-*- coding:utf-8 -*-

import configparser
import os
import sys
import serial
import socket
import json
import traceback
import ctypes
import psycopg2
import sqlite3
import time
import threading
import queue
import hashlib
import logging
import logging.handlers

from datetime import datetime,timedelta

from BD3Client import *

from DLLib import *
from BDLib import *


class BD3Server:

    rmccnt = 0
    clis = []
    com = []
    bdtx = queue.Queue()
    recved = False
    isreport = True
    jsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    gwsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tsrev = []
    bd_soc = []
    bd_send = queue.Queue()
    dl_soc = []
    dl_send = queue.Queue()
    pt_soc = []
    pt_send = queue.Queue()
    mst_ip = []
    mst_soc = []
    mst_send = queue.Queue()
    bout = ctypes.create_string_buffer(4096)

    def __init__(self):
        try:
            if not os.path.exists("DataLog"):
                os.makedirs("DataLog")

            if not os.path.exists("BDLog"):
                os.makedirs("BDLog")

            if not os.path.exists("PTLog"):
                os.makedirs("PTLog")

            self._conffn = os.path.basename(sys.argv[0]).split('.')[0] + '.ini'
            self.conf = configparser.ConfigParser()
            self.conf.read(self._conffn, 'utf-8')
            pver = self.conf.get('config', 'pver')
            if pver == 'NW12':
                self.pfDLP = NW12
            elif pver == 'GW3761':
                self.pfDLP = GW3761
            elif pver == 'OOP698':
                self.pfDLP = OOP698
            elif pver == 'PW101':
                self.pfDLP = PW101
            elif pver == 'PW104':
                self.pfDLP = PW104
            elif pver == 'I1':
                self.pfDLP = I1
            elif pver == 'GW3761/698':
                self.pfDLP = GW3761_698
            else:
                self.pfDLP = None

            self.zip = ctypes.CDLL(os.path.join(os.path.dirname(os.path.abspath(__file__)), self.conf.get('config', 'zipl')))

            self._dbtype = self.conf.getint('config', 'dbtype')
            self._connstr = self.conf.get('config', 'connstr')
            self.dbOpen()

            com = self.conf.get('config', 'com')
            if com:
                coms = com.split(',')
                for com in coms:
                    self.com.append(serial.Serial(com, self.conf.getint('config', 'baud')))
            else:
                self._tipaddr = (self.conf.get('config', 'tip'), self.conf.getint('config', 'tport'))
                uport = self.conf.get('config', 'uport')
                if uport:
                    self._uport = int(uport)
                    self.tsoc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                    self.tsoc.bind(('0.0.0.0', self._uport))
                else:
                    self.tsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self.ZHJSocConn()

            self._bdge = self.conf.get('config', 'bdge')
            self._dsmax = self.conf.getint('config', 'dsmax')
            self._dsspan = self.conf.getint('config', 'dsspan')

            gwip = self.conf.get('config', 'gwip')
            if gwip:
                self._gwip = (gwip, self.conf.getint('config', 'gwport'))

            jsonip = self.conf.get('config', 'jsonip')
            if jsonip:
                self._jsonip = (jsonip, self.conf.getint('config', 'jsonport'))
                self._loginstr = self.conf.get('config', 'loginstr').encode()

            bdinit = self.conf.get('config', 'bdinit')
            self._bdinit = bdinit.split('/') if bdinit else []
            self._bdlist = self.conf.get('config', 'list').split(',')
            freqlist = self.conf.get('config', 'freq').split(',')
            self.bdfreq = list(map(int, freqlist))
            self.bdcnt = list(map(int, freqlist))
            idxlist = self.conf.get('config', 'index').split(',')
            self._bdidx = list(map(int, idxlist))
            self._bdcd = self.conf.getint('config', 'cooldown')
            self.bdcd = 0
            self._len = self.conf.getint('config', 'len') - 1
            self._freqout = self.conf.getint('config', 'freqout')

            self._retry = self.conf.getint('config', 'retry')
            self._rmc = self.conf.getint('config', 'rmc')
            self._show = tuple(self.conf.get('config', 'show').split(','))
            self._kaspan = self.conf.getint('config', 'kaspan')
            self._katext = self.conf.get('config', 'katext').split('\n')

            self._ip = self.conf.get('config', 'ip').split(',')
            self._port = self.conf.get('config', 'port')
            port = list(map(int, self._port.split(',')))

            for i in range(len(self._ip)):
                self.mst_ip.append((self._ip[i], port[i]))
                self.mst_soc.append(socket.socket(socket.AF_INET, socket.SOCK_STREAM))

            self._dlport = self.conf.getint('config', 'dlport')
            self._bdport = self.conf.getint('config', 'bdport')
            self._ptport = self.conf.getint('config', 'ptport')

            self._loglevel = self.conf.getint('config', 'level')
            self._logdays = self.conf.getint('config', 'days')
            self._logspace = self.conf.getint('config', 'logspace')
            self.logger = self._getLogger()
            self.ptlogger = self._getPTLogger()
            self.gwlogger = self._getGWLogger()
            self.logger.critical('BD3Server v3.10 start....')
        except:
            traceback.print_exc()
            input('Press any key to close this window')
            os._exit(0)


    def _getLogger(self):
        logger = logging.getLogger('Server')

        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)
        handler = logging.handlers.TimedRotatingFileHandler('BD3Server.log', when='MIDNIGHT', interval=1, backupCount=self._logdays)
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)

        logger.setLevel(self._loglevel)

        return logger

    def _getGWLogger(self):
        logger = logging.getLogger('SecGW')

        handler = logging.handlers.TimedRotatingFileHandler('SecGW.log', when='MIDNIGHT', interval=1, backupCount=self._logdays)
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)
        logger.setLevel(self._loglevel)

        return logger

    def _getPTLogger(self):
        logger = logging.getLogger('PTLog')

        handler = logging.handlers.TimedRotatingFileHandler(os.path.join('PTLog', "PTLog.log"), when='MIDNIGHT', interval=1, backupCount=self._logdays)
        handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(message)s'))
        logger.addHandler(handler)
        logger.setLevel(self._loglevel)

        return logger


    def ReadConfigTxt(self):
        param_dict = {}
        config_dict = {}

        param_dict["type"] = "re_param"
        config_dict["protocal"] = self.conf.get('config', 'pver')
        config_dict["pver"] = self.conf.get('config', 'pver')
        config_dict["ip"] = self.conf.get('config', 'ip')
        config_dict["port"] = self.conf.get('config', 'port')
        config_dict["s_port"] = self.conf.get('config', 'ptport')
        config_dict["bdport"] = self.conf.get('config', 'bdport')
        config_dict["dlport"] = self.conf.get('config', 'dlport')
        config_dict["days"] = self.conf.get('config', 'days')
        config_dict["bdge"] = self.conf.get('config', 'bdge')
        config_dict["com_num"] = self.conf.get('config', 'com')
        config_dict["baud"] = self.conf.get('config', 'baud')
        config_dict["t_ip"] = self.conf.get('config', 'tip')
        config_dict["t_port"] = self.conf.get('config', 'tport')
        config_dict["bdinit"] = self.conf.get('config', 'bdinit')
        config_dict["list_card"] = self.conf.get('config', 'list')
        config_dict["freq"] = self.conf.get('config', 'freq')
        config_dict["framelen"] = self.conf.get('config', 'len')
        config_dict["retry"] = self.conf.get('config', 'retry')
        config_dict["jsonip"] = self.conf.get('config', 'jsonip')
        config_dict["jsonport"] = self.conf.get('config', 'jsonport')
        config_dict["jsonlogin"] = self.conf.get('config', 'loginstr')
        config_dict["kaspan"] = self.conf.get('config', 'kaspan')
        config_dict["katext"] = ''.join(self.conf.get('config', 'katext'))
        param_dict["param"] = config_dict
        d = json.dumps(param_dict) + "\r\n"

        self.ptlogger.info("[PT>] " + d)
        self.pt_send.put(d.encode())


    def MstSocConn(self, i):
        self.mst_soc[i].close()
        self.mst_soc[i] = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        while self.mst_soc[i].connect_ex(self.mst_ip[i]):
            time.sleep(1)
        self.mst_soc[i].sendall(("$0000000,0600,%s;EMERG\r\n" % self._bdge).encode())

    def GwSocConn(self):
        self.gwsoc.close()
        self.gwsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        while self.gwsoc.connect_ex(self._gwip):
            time.sleep(1)

    def ZHJSocConn(self):
        if hasattr(self, '_uport'):
            return
        self.tsoc.close()
        self.tsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        while self.tsoc.connect_ex(self._tipaddr):
            time.sleep(1)

    def JSONSocConn(self):
        self.jsoc.close()
        self.jsoc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        while self.jsoc.connect_ex(self._jsonip):
            time.sleep(1)
        if self._loginstr:
            self.jsoc.sendall(self._loginstr + b'\r\n')

    def printfNow(self):
        return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())


    def dbOpen(self):
        if self._dbtype == 1:
            self.db = sqlite3.connect(self._connstr, check_same_thread = False)
        elif self._dbtype == 2:
            self.db = psycopg2.connect(self._connstr)
        else:
            return
        self.cur = self.db.cursor()


    def dbInsert(self, bdid):
        """
        向数据库插入新的BD记录
        
        参数:
            bdid (int): BD标识符，用于唯一标识一条记录
            
        返回值:
            无
            
        功能说明:
            检查指定ID的记录是否已存在，如果不存在则插入新记录
        """
        try:
            # 检查数据库类型是否有效
            if self._dbtype:
                # 查询是否存在相同ID的记录
                self.cur.execute("SELECT id FROM bdlist WHERE id=%d" % bdid)
                rows = self.cur.fetchall()
                # 如果没有找到记录，则插入新记录
                if not len(rows):
                    self.cur.execute("INSERT INTO bdlist (id, create_time) VALUES (%d, '%s')" % (bdid, self.printfNow()))
                    self.db.commit()
        except:
            self.logger.error(traceback.format_exc())
            self.dbOpen()


    def dbUpName(self, bdid, name):
        """
        更新数据库中BD记录的名称信息
        
        参数:
            bdid (int): BD标识符，用于定位要更新的记录
            name (str): 终端ID名称，用于更新terid字段
            
        返回值:
            无
            
        功能说明:
            根据BDID更新记录的终端ID和更新时间，仅在数据库类型为2时执行
        """
        try:
            # 仅在数据库类型为2时执行更新操作
            if self._dbtype == 2:
                self.cur.execute("UPDATE bdlist SET terid='%s', update_time='%s' WHERE id=%d" % (name, self.printfNow(), bdid))
                self.db.commit()
        except:
            self.logger.error(traceback.format_exc())
            self.dbOpen()


    def dbUpInfo(self, bdid, loc1, loc2, ver, bsi):
        """
        更新数据库中BD记录的详细信息
        
        参数:
            bdid (int): BD标识符，用于定位要更新的记录
            loc1 (int): 位置信息1
            loc2 (int): 位置信息2
            ver (str): 版本信息
            bsi (str): BSI信息
            
        返回值:
            无
            
        功能说明:
            根据BDID更新记录的位置、版本、BSI和更新时间等详细信息
        """
        try:
            # 检查数据库类型是否有效
            if self._dbtype:
                self.cur.execute("UPDATE bdlist SET loc1=%d, loc2=%d, ver='%s', bsi='%s', update_time='%s' WHERE id=%d" % (loc1, loc2, ver, bsi, self.printfNow(), bdid))
                self.db.commit()
        except:
            self.logger.error(traceback.format_exc())
            self.dbOpen()


    def BDTxSend(self, id, d):
        """
        发送数据到BDTX队列，支持数据分片传输
        
        参数:
            id: 数据包标识符
            d: 要发送的字节数据
            
        返回值:
            无
        """
        # 如果数据长度小于等于最大长度限制，直接发送
        if len(d) <= self._len:
            self.bdtx.put("$%d,8%s" % (id, d.hex().upper()))
        else:
            # 数据长度超过限制，需要分片处理
            xd = []
            while len(d):
                xd.append(d[:self._len])
                d = d[self._len:]
            # 发送除最后一片外的所有数据片
            for i in range(0, len(xd) - 1):
                self.bdtx.put("$%d,%X%s" % (id, i, xd[i].hex().upper()))
            # 发送最后一片数据，使用特殊标记
            self.bdtx.put("$%d,%X%s" % (id, len(xd) + 7, xd[-1].hex().upper()))


    def GwDecode(self, d):
        """
        解码网关数据包
        
        参数:
            d: 待解码的数据包，格式为[type, len_low, len_high, data...]
            
        返回值:
            tuple: (type, data) 其中type为数据包类型，data为实际数据内容
        """
        type = d[0]
        len = d[1] + (d[2] << 8)
        s = d[3:len]
        return type, s

    def GwSend(self, type, d):
        """
        发送数据到网关
        
        参数:
            type: 数据包类型
            d: 要发送的数据内容
            
        返回值:
            bool: 发送成功返回True，失败返回False
        """
        if not hasattr(self, '_gwip'):
            return False
        
        # 构造数据包头部
        tx = bytearray(3)
        tx[0] = type
        tx[1] = len(d)%256
        tx[2] = len(d)>>8
        tx.extend(d)
        
        # 发送数据包
        try:
            self.gwsoc.sendall(tx)
        except Exception as e:
            self.GwSocConn()
            return False
        
        # 记录发送日志
        log = ">>[%d] %s" % (type, d[:-2].decode(errors = 'ignore'))
        self.gwlogger.info(log)
        return True

    def MstRecv(self, socket, rd):
        """
        处理从主设备接收到的数据。该函数解析接收数据并根据协议类型进行处理，
        包括客户端管理、日志记录以及数据转发等操作。

        参数:
            socket: 接收数据的套接字对象。
            rd (bytes): 从套接字读取到的原始数据。

        返回值:
            无返回值。
        """
        self.logger.debug(rd)
        rd = rd.split(b'\r\n')
        for msg in rd:
            r = msg.split(b',', 1)
            if len(r) < 2:
                continue
            h = r[0].decode(errors='ignore')
            bdid = int(h[1:])
            
            # 处理以 '#' 开头的消息（可能是控制或配置信息）
            if h[0] == '#':
                self.bdtx.put(h + ',' + r[1].hex().upper())

            # 处理以 '$' 开头的消息（通常是终端上传的数据）
            if h[0] == '$':
                # 查找对应的客户端对象，如果不存在则创建新实例
                for c in self.clis:
                    if c.id == bdid:
                        break
                else:
                    c = BD3Client(self, bdid)
                    self.clis.append(c)

                # 将消息体转换为字节格式
                d = bytes.fromhex(r[1].decode(errors='ignore'))

                # 根据是否启用 DLP 协议处理逻辑分支
                if not self.pfDLP:
                    self.bdtx.put("$%d,%s" % (c.id, d.hex().upper()))
                else:
                    # 如果是 DL/T645-1997 协议帧（起始符为 0x68）则进一步解析
                    if d[0] == 0x68:
                        l, name = self.pfDLP(d, bdid)
                        d = d[:l]
                        # 更新客户端名称及日志句柄
                        if c.name != name:
                            c.name = name
                            c.terlog = c._getTerLogger()
                        # 记录通信日志
                        if self._logspace:
                            slog = ' '.join(['[DL>]'] + ["%02X" % i for i in d])
                        else:
                            slog = '[DL>] ' + d.hex().upper()
                        c.terlog.warning(slog)
                        
                        # 若使用 BD3 网关模式，则尝试发送数据；失败时缓存待重发
                        if self._bdge == 'BD3':
                            if not self.GwSend(1, msg + b'\r\n'):
                                c.tx.append(d)
                                c.tlast = int(time.time())
                                d = self.BDTxEncode(c, len(c.tx))
                                if len(d) > self._len:
                                    if len(c.tx) < 2:
                                        self.BDTxSend(c.id, d)
                                        c.tx = []
                                    else:
                                        d = self.BDTxEncode(c, len(c.tx) - 1)
                                        self.BDTxSend(c.id, d)
                                        c.tx = [c.tx[-1]]

                    # 非 DL/T645 帧直接透传
                    if self._bdge == 'BD3':
                        if d[0] != 0x68:
                            self.BDTxSend(c.id, d)
                    else:
                        # BD2 模式下的分包传输处理
                        if len(d) <= BD2_SIZE:
                            self.bdtx.put("$%d,C%s" % (c.id, d.hex().upper()))
                        else:
                            xd = []
                            while len(d):
                                xd.append(d[:BD2_SIZE - 2])
                                d = d[BD2_SIZE - 2:]
                            self.bdtx.put("$%d,8%02d%s" % (c.id, len(xd), xd[0].hex().upper()))
                            for i in range(1, len(xd) - 1):
                                self.bdtx.put("$%d,0%02d%s" % (c.id, i, xd[i].hex().upper()))
                            self.bdtx.put("$%d,4%02d%s" % (c.id, len(xd) - 1, xd[-1].hex().upper()))

            # 回复确认消息，包含校验码
            res = "!%d,%s\r\n" % (bdid, hashlib.md5(r[1]).hexdigest())
            socket.sendall(res.encode())


    def BD2Recv(self, bdid, mode, d):
        """
        处理BD2协议接收数据的函数
        
        参数:
            bdid: 设备ID标识符
            mode: 数据处理模式，1表示特殊处理模式，其他值表示普通模式
            d: 接收到的数据字符串
            
        返回值:
            无返回值
        """
        # 查找是否存在对应的客户端对象，如果不存在则创建新的客户端
        for c in self.clis:
            if c.id == bdid:
                break
        else:
            c = BD3Client(self, bdid)
            self.clis.append(c)

        c.bdlog.info('[BD<] ' + d)
        
        # 根据不同的模式处理接收到的数据
        if mode == 1:
            # 特殊处理模式：根据DLP标志决定数据发送方式
            if not self.pfDLP:
                self.mst_send.put(("$%d,%s" % (bdid, d)).encode())
            else:
                try:
                    ld = d.split(',')
                    c.BD2TXR(bytes.fromhex('0' + ld[0]))
                except Exception as err:
                    self.logger.error("BD2TXR error %s: %s" % (err, d))
        else:
            # 普通模式：使用GBK编码发送数据
            self.mst_send.put(("#%d,%s" % (bdid, d)).encode('gbk'))


    def BD3Recv(self, bdid, mode, d):
        """
        处理BD3协议接收数据的函数
        
        参数:
            bdid: 客户端ID标识
            mode: 数据处理模式(2-十六进制模式, 3-GBK编码模式等)
            d: 接收到的数据内容
            
        返回值:
            无返回值
        """
        # 查找或创建对应的客户端对象
        for c in self.clis:
            if c.id == bdid:
                break
        else:
            c = BD3Client(self, bdid)
            self.clis.append(c)

        c.bdlog.info('[BD<] ' + d)
        
        # 根据不同模式处理接收到的数据
        if mode == 2:
            # 模式2: 处理十六进制数据
            if not self.pfDLP:
                # 如果不使用DLP协议，直接转发数据
                self.mst_send.put(("$%d,%s" % (bdid, d)).encode())
            else:
                # 使用DLP协议解析数据
                try:
                    da = bytes.fromhex('0' + d)
                    if da[0] == 8 and da[1] == 0xC0:
                        # 新协议格式处理
                        c.BD3RxNew(da[2:])
                    else:
                        # 传统协议格式处理
                        c.BD3TXR(0, da[0], da[1:])
                except Exception as err:
                    self.logger.error("BD3TXR error %s: %s" % (err, d))
        else:
            # 其他模式: 根据模式3或其他模式处理数据并发送
            d = bytes.fromhex(d[2:]) if mode == 3 else d.encode('gbk')
            self.mst_send.put(("#%d," % bdid).encode() + d)


    ''' 接收平台管理数据 '''
    def PTRecv(self, steam):
        """
        处理从平台接收到的数据流，解析指令并执行相应操作。

        参数:
            steam (str): 从平台接收的原始数据字符串，通常为JSON格式封装的控制指令或配置信息。

        返回值:
            无返回值。
        """

        # 判断数据是否以 '{' 开头，表示为 JSON 格式数据
        if steam[0] == '{':
            # 截取从开头到最后一对花括号的内容，确保获取完整的 JSON 数据
            steam = steam[0:steam.rfind('}') + 1]
            # 将单引号替换为双引号，使其符合标准 JSON 格式
            steam = steam.replace("'", '"')
            # 解析 JSON 数据
            res = json.loads(steam)
            # 记录接收到的平台指令日志
            self.ptlogger.info('[PT<] ' + str(res))
            # 获取指令类型
            s_type = res['type']

            # 如果是控制类指令
            if (s_type == 's_control'):
                s_data = res['instruct']

                # 根据不同指令执行对应操作
                if s_data == 'restart':
                    # 重启系统
                    os.system("shutdown -r -t 1")

                if s_data == 'suspend':
                    # 暂停上报数据
                    print("Recv data suspend.")
                    self.isreport = False

                if s_data == 'activate':
                    # 激活上报数据
                    print("Recv data activate.")
                    self.isreport = True

                if s_data == 'param':
                    # 重新读取本地配置文件
                    self.ReadConfigTxt()

            # 如果是编辑参数类指令
            elif (s_type == 'edit_param'):
                s_data = res['data']
                
                # 更新配置项（如果存在对应字段）
                if 'list_card' in s_data:
                    self.conf.set('config', 'list', s_data['list_card'])
                if 'freq' in s_data:
                    self.conf.set('config', 'freq', s_data['freq'])
                if 'bdinit' in s_data:
                    self.conf.set('config', 'bdinit', s_data['bdinit'])
                if 'framelen' in s_data:
                    self.conf.set('config', 'len', s_data['framelen'])
                if 'jsonip' in s_data:
                    self.conf.set('config', 'jsonip', s_data['jsonip'])
                if 'jsonport' in s_data:
                    self.conf.set('config', 'jsonport', s_data['jsonport'])
                if 'jsonlogin' in s_data:
                    self.conf.set('config', 'loginstr', s_data['jsonlogin'])

                # 将更新后的配置写入配置文件
                with open(self._conffn, 'w+', encoding='utf-8') as f:
                    self.conf.write(f)

                # 重新加载配置文件内容
                self.conf.read(self._conffn, 'utf-8')

                # 将修改后的最新配置返回给平台
                self.ReadConfigTxt()


    def thd_MstRecv(self, i):
        self.MstSocConn(i)
        while True:
            try:
                d = self.mst_soc[i].recv(4096)
                if not d:
                    raise Exception('Disconnected')
                self.MstRecv(self.mst_soc[i], d)
            except:
                self.logger.error(traceback.format_exc())
                self.MstSocConn(i)


    def thd_MstSend(self):
        while True:
            try:
                d = self.mst_send.get()
                if self.isreport:
                    for s in self.mst_soc:
                        try:
                            s.sendall(d + b'\r\n')
                        except:
                            pass
            except:
                self.logger.error(traceback.format_exc())


    def BDTxEncode(self, c, size):
        """
        将数据编码并压缩后返回。

        参数:
            c: 包含待编码数据的对象，其 tx 属性为二维列表。
            size: 表示处理 c.tx 中前 size 个元素。

        返回值:
            bytes: 压缩后的数据（如果压缩成功），否则返回原始数据。
        """
        bin = ctypes.create_string_buffer(4096)

        l = 0
        # 将二维列表 c.tx 的前 size 行数据依次复制到缓冲区 bin 中
        for i in range(0, size):
            for d in c.tx[i]:
                bin[l] = d
                l += 1

        bout = ctypes.create_string_buffer(4096)
        # 调用压缩函数对数据进行压缩
        zl = self.zip.EnData(bout, bin, l, 1)
        # 如果压缩后的长度小于原长度，则返回压缩数据；否则返回原始数据
        if zl < l:
            return bout.raw[:zl]
        return bin.raw[:l]


    def thd_DLPort_Send(self):
        """
        从队列 dl_send 中获取数据，并发送给所有 dl_soc 中的 socket 连接。
        """
        while True:
            try:
                d = self.dl_send.get()
                # 遍历所有已连接的 socket 并发送数据
                for s in self.dl_soc:
                    s.sendall(d.encode())
            except:
                self.logger.error(traceback.format_exc())


    def thd_DLPort_Recv(self, s):
        """
        接收指定 socket 的数据并传递给 MstRecv 方法处理。

        参数:
            s: 用于接收数据的 socket 对象。
        """
        self.dl_soc.append(s)
        while True:
            try:
                d = s.recv(4096)
                if not d:
                    break
                self.MstRecv(s, d)
            except:
                self.logger.error(traceback.format_exc())
                break
        self.dl_soc.remove(s)


    def thd_DLPort_Accept(self):
        """
        监听 DL 端口，接受新的连接，并为每个连接启动一个接收线程。
        """
        try:
            a = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            a.bind(('0.0.0.0', self._dlport))
            a.listen(5)
        except:
            traceback.print_exc()
            input('Press any key to close this window')
            os._exit(0)

        while True:
            try:
                s, addr = a.accept()
                # 为新连接启动接收线程
                threading.Thread(target=self.thd_DLPort_Recv, args=(s,)).start()
            except:
                self.logger.error(traceback.format_exc())


    def thd_BDPort_Send(self):
        """
        从队列 bd_send 中获取二进制数据，并发送给所有 bd_soc 中的 socket 连接。
        """
        while True:
            try:
                d = self.bd_send.get()
                # 遍历所有已连接的 socket 并发送数据
                for s in self.bd_soc:
                    s.sendall(d)
            except:
                self.logger.error(traceback.format_exc())


    def thd_BDPort_Recv(self, s):
        """
        接收指定 socket 的数据，按换行符分割后放入 bdtx 队列。

        参数:
            s: 用于接收数据的 socket 对象。
        """
        self.bd_soc.append(s)
        while True:
            try:
                d = s.recv(4096)
                if not d:
                    break
                # 按照 \r\n 分割接收到的数据
                d = d.split(b'\r\n')
                for l in d:
                    # 忽略解码错误并将字符串放入队列
                    self.bdtx.put(l.decode(errors='ignore'))
            except:
                self.logger.error(traceback.format_exc())
                break
        self.bd_soc.remove(s)


    def thd_BDPort_Accept(self):
        """
        监听 BD 端口，接受新的连接，并为每个连接启动一个接收线程。
        """
        try:
            a = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            a.bind(('0.0.0.0', self._bdport))
            a.listen(5)
        except:
            traceback.print_exc()
            input('Press any key to close this window')
            os._exit(0)

        while True:
            try:
                s, addr = a.accept()
                # 为新连接启动接收线程
                threading.Thread(target=self.thd_BDPort_Recv, args=(s,)).start()
            except:
                self.logger.error(traceback.format_exc())


    def thd_BDPort_Accept(self):
        """
        监听 BD 端口，接受新的连接，并为每个连接启动一个接收线程。
        （注意：该函数与上一个函数重复定义）
        """
        try:
            a = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            a.bind(('0.0.0.0', self._bdport))
            a.listen(5)
        except:
            traceback.print_exc()
            input('Press any key to close this window')
            os._exit(0)

        while True:
            try:
                s, addr = a.accept()
                # 为新连接启动接收线程
                threading.Thread(target=self.thd_BDPort_Recv, args=(s,)).start()
            except:
                self.logger.error(traceback.format_exc())


    ''' 发送数据给中间件平台 '''
    def thd_PTPort_Send(self):
        while True:
            try:
                d = self.pt_send.get()
                for s in self.pt_soc:
                    s.sendall(d)
            except:
                self.logger.error(traceback.format_exc())


    ''' 接收中间件平台数据 '''
    def thd_PTPort_Recv(self, s):
        self.pt_soc.append(s)
        while True:
            try:
                d = s.recv(4096)
                if not d:
                    break
                # 解析平台下发的命令
                self.PTRecv(d.decode(errors = 'ignore'))
            except:
                self.logger.error(traceback.format_exc())
        self.pt_soc.remove(s)


    ''' 监听中间件平台连接请求 '''
    def thd_PTPort_Accept(self):
        try:
            a = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            a.bind(('0.0.0.0', self._ptport))
            a.listen(5)
        except:
            traceback.print_exc()
            input('Press any key to close this window')
            os._exit(0)

        while True:
            try:
                s, addr = a.accept()
                threading.Thread(target=self.thd_PTPort_Recv, args=(s,)).start()
            except:
                self.logger.error(traceback.format_exc())


    def ZHJWrite(self, d, i):
        """
        向指定的北斗通信通道发送数据。

        参数:
            d (bytes): 要发送的数据。
            i (int): 通道索引，用于标识发送通道。

        返回值:
            无返回值。
        """
        if hasattr(self, 'tsoc'):
            if hasattr(self, '_uport'):
                self.tsoc.sendto(d, self._tipaddr)
            else:
                try:
                    self.tsoc.sendall(d)
                except Exception as e:
                    self.logger.error(traceback.format_exc())
                    self.MstSocConn(i)
        else:
            self.com[i].write(d)
        self.bdcd = self._bdcd
        print(("TX[%d]" % i) + d.decode('gbk', errors='ignore'))


    def ZHJSocRecv(self):
        """
        接收北斗通信通道的数据。

        返回值:
            bytes: 接收到的数据。
        """
        while True:
            if len(self.tsrev):
                if len(self.tsrev) > 1 or b'*' in self.tsrev[0]:
                    return self.tsrev.pop(0)
            try:
                if hasattr(self, '_uport'):
                    d, addr = self.tsoc.recvfrom(4096)
                else:
                    d = self.tsoc.recv(4096)
                    if not d:
                        raise Exception('Disconnected')
            except:
                self.logger.error(traceback.format_exc())
                self.ZHJSocConn()
            else:
                if len(self.tsrev):
                    d = self.tsrev[0] + d
                self.tsrev = d.splitlines(True)


    def JSONSocRecv(self):
        """
        接收JSON通信通道的数据。

        返回值:
            bytes: 接收到的数据。
        """
        while True:
            if len(self.tsrev):
                if len(self.tsrev) > 1 or b'\r\n' in self.tsrev[0]:
                    return self.tsrev.pop(0)
            try:
                d = self.jsoc.recv(4096)
                if not d:
                    raise Exception('Disconnected')
            except:
                self.logger.error(traceback.format_exc())
                self.JSONSocConn()
            else:
                if len(self.tsrev):
                    d = self.tsrev[0] + d
                self.tsrev = d.split(b'\r\n')


    def RMC(self, d):
        """
        解析并处理RMC（推荐最小定位信息）数据，用于同步系统时间。

        参数:
            d (str): RMC数据字符串。

        返回值:
            无返回值。
        """
        d = d.split(',')
        if len(d) < 10:
            raise Exception('RMC len')
        if len(d[1]) < 6 or len(d[9]) < 6:
            raise Exception('RMC no datetime')
        self.rmccnt += 1
        if self.rmccnt > self._rmc:
            self.rmccnt = 0
            utc = datetime.strptime("20%s%s%s %s" % (d[9][4:], d[9][2:4], d[9][:2], d[1][:6]), '%Y%m%d %H%M%S')
            # loc = utc - timedelta(seconds=time.timezone)
            loc = utc + timedelta(hours=8)
            cmd = "date -s \"%s\"" % loc.strftime('%Y-%m-%d %H:%M:%S')
            os.system(cmd)


    def thd_ZHJSend(self, num):
        """
        北斗发送线程，负责初始化北斗模块并周期性发送数据。

        参数:
            num (int): 发送通道数量。

        返回值:
            无返回值。
        """
        kacnt = 0
        tlka = 0
        tlout = 0
        cofq = 0
        try:
            for cmd in self._bdinit:
                time.sleep(0.5)
                for i in range(0, num):
                    self.ZHJWrite(BDXor(cmd.encode()), i)
        except:
            self.logger.error(traceback.format_exc())

        while True:
            try:
                time.sleep(1)
                tnow = int(time.time())

                # BD3模式下，定期发送频率校准命令
                if self._bdge == 'BD3':
                    cofq += 1
                    if not cofq % 10:
                        for i in range(0, num):
                            self.ZHJWrite("CCOFQ,1,2*5B\r\n".encode(), i)

                # 更新各通道的发送计数器
                for i in range(0, len(self.bdcnt)):
                    if self.bdcnt[i]:
                        self.bdcnt[i] -= 1

                # 定期输出通道状态信息
                if self._freqout:
                    if tnow - tlout >= self._freqout:
                        tlout = tnow
                        out = "<DATA: %d> <FREQ: %s> <CD: %d>\r\n" % (
                            self.bdtx.qsize(), ', '.join(map(str, self.bdcnt)), self.bdcd)
                        self.bd_send.put(out.encode())

                # 冷却时间处理
                if self.bdcd:
                    self.bdcd -= 1
                    continue

                # 检查是否有空闲通道
                for idle in range(0, len(self.bdcnt)):
                    if not self.bdcnt[idle]:
                        break
                else:
                    continue

                # 如果没有待发送数据，则发送心跳包
                if not self.bdtx.qsize():
                    if self._kaspan:
                        if tnow - tlka >= self._kaspan:
                            tlka = tnow
                            if not self.bdcnt[0]:
                                if self._bdge == 'BD3':
                                    self.ZHJWrite(BDXor(self._katext[kacnt].encode()), self._bdidx[0])
                                else:
                                    self.ZHJWrite(
                                        BDXor((self._katext[kacnt] + (",%s" % self._bdlist[0])).encode()),
                                        self._bdidx[0])
                                self.bdcnt[0] = self.bdfreq[0]
                                kacnt = (kacnt + 1) % len(self._katext)
                    continue

                # 发送队列中的数据
                l = self.bdtx.get().split(',')
                if len(l) == 2:
                    bdid = int(l[0][1:])
                    if self._bdge == 'BD3':
                        if l[0][0] == '$':
                            tx = BDXor(("$CCTCQ,%d,2,1,2,%s,0" % (bdid, l[1])).encode())
                        if l[0][0] == '#':
                            tx = BDXor(("$CCTCQ,%d,2,1,3,A4%s,0" % (bdid, l[1])).encode())
                    else:
                        if l[0][0] == '$':
                            tx = BDXor(("$CCTXA,%d,1,1,%s,%s" % (bdid, l[1], self._bdlist[idle])).encode())
                        if l[0][0] == '#':
                            tx = BDXor(("$CCTXA,%d,1,2," % bdid).encode() + bytes.fromhex(l[1]) + (
                                        ",%s" % self._bdlist[idle]).encode())

                    self.logger.debug(tx)
                    for i in range(0, self._retry):
                        self.ZHJWrite(tx, self._bdidx[idle])
                        time.sleep(0.5)
                    self.bdcnt[idle] = self.bdfreq[idle]

                    # 查找或创建客户端对象
                    for c in self.clis:
                        if c.id == bdid:
                            break
                    else:
                        c = BD3Client(self, bdid)
                        self.clis.append(c)
                    c.bdlog.info("[BD>] %s" % l[1])

            except:
                self.logger.error(traceback.format_exc())


    def thd_BD2ZHJRecv(self, i):
        """
        北斗接收线程，负责接收北斗通信数据并进行处理。

        参数:
            i (int): 通道索引。

        返回值:
            无返回值。
        """
        dlast = ''
        while True:
            try:
                d = self.ZHJSocRecv() if hasattr(self, 'tsoc') else self.com[i].read_until(b'\r\n')
                self.logger.debug(d)
                self.recved = True
                if dlast != d:
                    dlast = d
                    self.bd_send.put(d)
                    d = d.decode('gbk', errors='ignore')
                    if not self._show or d[3:].startswith(self._show):
                        print(("RX[%d]" % i) + d)
                    if self._rmc and d[3:6] == 'RMC':
                        self.RMC(d)

                    try:
                        xr = d.split('*')
                        if len(xr) != 2:
                            raise Exception('no *')
                        xr = xr[0].split(',', 5)
                        if xr[0] != '$BDTXR':
                            continue
                        if len(xr) < 6:
                            raise Exception('split')
                    except Exception as err:
                        self.logger.error("Failed to resolve %s: %s" % (err, d))
                    else:
                        bdid = int(xr[2])
                        bdda = xr[5]
                        self.BD2Recv(bdid, int(xr[3]), bdda)
            except:
                self.logger.error(traceback.format_exc())


    def thd_BD3ZHJRecv(self, i):
        """
        北斗三代终端接收数据处理线程函数
        
        该函数持续监听并处理来自北斗三代终端的数据，包括数据接收、解析、
        记录和转发等功能。支持通过socket或串口两种通信方式。
        
        参数:
            i (int): 终端编号，用于标识不同的北斗终端设备
            
        返回值:
            无返回值，该函数作为线程持续运行
        """
        while True:
            try:
                # 接收数据：根据通信方式选择socket或串口读取
                d = self.ZHJSocRecv() if hasattr(self, 'tsoc') else self.com[i].read_until(b'\r\n')
                self.logger.debug(d)
                self.recved = True
                self.bd_send.put(d)
                
                # 数据解码和显示处理
                d = d.decode('gbk', errors = 'ignore')
                if not self._show or d[3:].startswith(self._show):
                    print(("RX[%d]" % i) + d)
                    
                # RMC数据处理
                if self._rmc and d[3:6] == 'RMC':
                    self.RMC(d)

                # 北斗TCI数据解析处理
                try:
                    xr = d.split('*')
                    if len(xr) != 2:
                        raise Exception('no *')
                    xr = xr[0].split(',', 7)
                    if xr[0] != '$BDTCI':
                        continue
                    if len(xr) < 8:
                        raise Exception('split')
                except Exception as err:
                    self.logger.error("Failed to resolve %s: %s" % (err, d))
                else:
                    # 提取北斗数据并调用接收处理函数
                    bdid = int(xr[1])
                    bdda = xr[7]
                    self.BD3Recv(bdid, int(xr[5]), bdda)
            except:
                self.logger.error(traceback.format_exc())


    def thd_JSONRecv(self):
        self.JSONSocConn()
        while True:
            try:
                d = self.JSONSocRecv()
                self.logger.debug(d)
                self.bd_send.put(d)
                d = json.loads(d)

                self.BD3Recv(int(d['fromCard']), 2, d['content'])

            except:
                self.logger.error(traceback.format_exc())


    def thd_BDSend(self):
        """
        BDS数据发送线程函数
        
        该函数作为一个后台线程运行，定期检查客户端队列中的待发送数据，
        并将超过3秒未发送的数据进行编码后发送出去。
        
        参数:
            无
            
        返回值:
            无
            
        异常:
            所有异常都会被捕获并记录到日志中
        """
        while True:
            try:
                # 每秒检查一次待发送数据
                time.sleep(1)
                tnow = int(time.time())
                
                # 遍历所有客户端，检查是否有待发送数据
                for c in self.clis:
                    # 跳过没有待发送数据的客户端
                    if not len(c.tx):
                        continue
                    
                    # 如果距离上次发送时间超过3秒，则发送数据
                    if tnow - c.tlast > 3:
                        # 对待发送数据进行编码
                        d = self.BDTxEncode(c, len(c.tx))
                        # 发送编码后的数据
                        self.BDTxSend(c.id, d)
                        # 清空已发送的数据队列
                        c.tx = []
            except:
                # 记录所有异常信息
                self.logger.error(traceback.format_exc())


    def thd_SecGWRecv(self):
        """
        安全网关接收数据处理线程函数
        
        该函数负责接收来自网关的数据包，解析数据包内容，并根据数据包类型进行相应的处理。
        主要功能包括：建立网关连接、接收数据、解析协议、分发数据到对应的客户端等。
        
        参数:
            无
            
        返回值:
            无
            
        异常处理:
            当发生异常时，会记录错误日志并重新建立网关连接
        """
        self.GwSocConn()
        while True:
            try:
                # 接收网关数据，缓冲区大小为4096字节
                orx = self.gwsoc.recv(4096)
                if not orx:
                    raise Exception('Disconnected')
                
                # 解析数据包长度信息（小端序）
                l = orx[1] + (orx[2] << 8)
                
                # 提取并解码数据内容
                rx = orx[3:l+1].decode(errors = 'ignore')
                
                # 分割数据头和数据体
                r = rx.split(',', 1)
                if len(r) < 2:
                    continue
                    
                h = r[0]
                if h[0] != '$':
                    continue
                    
                # 记录接收到的数据日志
                self.gwlogger.info("<<[%d] %s" % (orx[0], rx))
                
                # 解析设备ID和数据内容
                bdid = int(h[1:])
                d = bytes.fromhex(r[1])                
                
                # 查找或创建对应的客户端对象
                for c in self.clis:
                    if c.id == bdid:
                        break
                else:
                    c = BD3Client(self, bdid)
                    self.clis.append(c)

                # 根据数据包类型进行不同处理
                if orx[0] == 1:
                    # 类型1数据包处理
                    if not self.pfDLP:
                        self.mst_send.put(orx[3:l+1])
                    else:
                        c.BD3TXR(0, 8, d)
                elif orx[0] == 2:
                    # 类型2数据包处理
                    if not self.pfDLP:
                        self.bdtx.put("$%d,%s" % (c.id, d.hex().upper()))
                    else:
                        if len(d) > self._len:
                            self.BDTxSend(c.id, d)
                        else:
                            c.tx.append(d)
                            c.tlast = int(time.time())
            except:
                # 异常处理：记录错误日志并重新连接
                self.logger.error(traceback.format_exc())
                self.GwSocConn()


    def Daemon(self):
        """
        守护进程函数，用于监控和维护程序运行状态
        
        该函数作为一个后台守护进程运行，主要功能包括：
        1. 定期检查接收状态并更新运行状态文件的时间戳
        2. 监控socket连接状态，超时后重新建立连接
        3. 捕获异常并记录错误日志
        
        参数:
            self: 类实例对象，包含recved、logger、tsoc等属性
            
        返回值:
            无返回值，该函数为无限循环运行
        """
        tlast = int(time.time())
        filename = os.path.basename(sys.argv[0]).split('.')[0] + '.running'
        while True:
            try:
                time.sleep(5)
                tnow = int(time.time())

                # 检查是否有接收到数据，如果有则更新运行状态文件的时间戳
                if self.recved:
                    self.recved = False
                    os.utime(filename, (tnow, tnow))
                    tlast = tnow

                # 检查socket连接状态，如果超过30秒没有活动则重新连接
                if hasattr(self, 'tsoc'):
                    if tnow - tlast > 30:
                        tlast = tnow
                        self.ZHJSocConn()
            except:
                # 捕获所有异常并记录错误堆栈信息
                self.logger.error(traceback.format_exc())


    def Start(self):
        for i in range(len(self.mst_ip)):
            threading.Thread(target=self.thd_MstRecv, args=(i,)).start()
        threading.Thread(target=self.thd_MstSend).start()

        num = 1 if hasattr(self, 'tsoc') else len(self.com)
        for i in range(0, num):
            if self._bdge == 'BD3':
                threading.Thread(target=self.thd_BD3ZHJRecv, args=(i,)).start()
            else:
                threading.Thread(target=self.thd_BD2ZHJRecv, args=(i,)).start()
        if self._bdge == 'BD3':
            threading.Thread(target=self.thd_BDSend).start()
        threading.Thread(target=self.thd_ZHJSend, args=(num,)).start()
        if hasattr(self, '_jsonip'):
            threading.Thread(target=self.thd_JSONRecv).start()
        if hasattr(self, '_gwip'):
            threading.Thread(target=self.thd_SecGWRecv).start()

        if self._bdport:
            threading.Thread(target=self.thd_BDPort_Accept).start()
            threading.Thread(target=self.thd_BDPort_Send).start()
        if self._dlport:
            threading.Thread(target=self.thd_DLPort_Accept).start()
            threading.Thread(target=self.thd_DLPort_Send).start()
        if self._ptport:
            threading.Thread(target=self.thd_PTPort_Accept).start()
            threading.Thread(target=self.thd_PTPort_Send).start()

        self.Daemon()


if __name__=='__main__':
    BD3Server().Start()
